package org.hilo.boot.core.code;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;

import org.jretty.util.ReflectionUtils.FieldCallback;

import org.hilo.boot.core.ReqDto;
import org.hilo.boot.core.ResDto;
import org.hilo.boot.core.UT;

/**
 * 自动生成和更新接口代码
 * 
 * @author zollty
 * @since 2018年6月19日
 */
public class ServiceCodeGenerator {
    
    private final String packageName;
    
    private String serviceName;
    
    private String module;
    
    private String outputDir;
    
    private String methodName;
    
    private String comment;
    
    private String author;
    
    private List<FieldMeta> params = new LinkedList<>();;
    
    private List<FieldMeta> results = new LinkedList<>();

    /**
     * @param packageName 接口包名（全局变量）
     * @param serviceName 类名前缀（区分大小写）
     */
    public ServiceCodeGenerator(String packageName, String serviceName) {
        this(packageName, serviceName, serviceName);
    }
    
    public ServiceCodeGenerator(String packageName, String serviceName, String module) {
        super();
        this.packageName = packageName;
        this.serviceName = serviceName;
        this.module = module;
    }
    
    public ServiceCodeGenerator defaultAuthor(String author) {
        this.author = author;
        return this;
    }
    
    public ServiceCodeGenerator defaultOutputDir(String outputDir) {
        this.outputDir = outputDir;
        return this;
    }
    
    public Meta name(String methodName, String comment) {
        return name(methodName, comment, author);
    }
    
    public Meta name(String methodName, String comment, String author) {
        ServiceCodeGenerator scg = new ServiceCodeGenerator(packageName, serviceName);
        scg.methodName = methodName;
        scg.author = author;
        scg.comment = comment;
        scg.outputDir = outputDir;
        scg.module = module;
        return scg.new Meta();
    }
    
    public class Meta {
    
    /**
     * 添加请求参数（FieldMeta）
     * @param type 参数类型
     * @param name 参数名称
     * @param comment 注释
     */
    @SuppressWarnings("rawtypes")
    public Meta params(Class type, String name, String comment) {
        params.add(new FieldMeta(name, type, comment));
        return this;
    }
    
    /**
     * 添加返回值（FieldMeta）
     * @param type 数据类型
     * @param name 数据名称
     * @param comment 注释
     */
    @SuppressWarnings("rawtypes")
    public Meta results(Class type, String name, String comment) {
        results.add(new FieldMeta(name, type, comment));
        return this;
    }
    
    
    protected void parseFieldInfo(FieldInfo info, final List<FieldMeta> list) {
        UT.Ref.doWithFields(info.getClass(), new FieldCallback() {
            @Override
            public void doWith(Field field) {
                if (Modifier.isStatic(field.getModifiers()) || field.getName().startsWith("this$")) {
                    return;
                }
                
                System.out.println(field.getGenericType().getTypeName() + " " + field.getName());

                FieldMeta fm = new FieldMeta();
                fm.setName(field.getName());
                fm.setPropertyType(field.getGenericType().getTypeName());
                
                Desc desc = field.getAnnotation(Desc.class);
                if (desc != null) {
                    System.out.println(desc.value());
                    fm.setComment(desc.value());
                }
                
                List<String> annotations = new LinkedList<>();
                fm.setAnnotations(annotations);
                
                Email email = field.getAnnotation(Email.class);
                if (email != null) {
                    System.out.println(email.message());
                    annotations.add("@Email");
                }
                
                NotNull notNull = field.getAnnotation(NotNull.class);
                if (notNull != null) {
                    System.out.println(notNull.message());
                    annotations.add("@NotNull");
                }
                
                NotEmpty notEmpty = field.getAnnotation(NotEmpty.class);
                if (notEmpty != null) {
                    System.out.println(notEmpty.message());
                    annotations.add("@NotEmpty");
                }
                
                NotBlank notBlank = field.getAnnotation(NotBlank.class);
                if (notBlank != null) {
                    System.out.println(notBlank.message());
                    annotations.add("@NotBlank");
                }
                
                list.add(fm);
            }
        });
    }
    
    /**
     * 添加请求参数（FieldMeta）
     * @param type 参数类型
     * @param name 参数名称
     * @param comment 注释
     */
    public Meta params(FieldInfo info) {
        parseFieldInfo(info, params);
        return this;
    }
    
    /**
     * 添加返回值（FieldMeta）
     * @param type 数据类型
     * @param name 数据名称
     * @param comment 注释
     */
    public Meta results(FieldInfo info) {
        parseFieldInfo(info, results);
        return this;
    }
    
    public static final String TEMPLATE_ENTITY = "/templates/api/entity.java.ftl";
    public static final String TEMPLATE_SERVICE = "/templates/api/service.java.ftl";
    
    public void build(boolean appendMethodToServiceName, boolean open) {
        build(outputDir, appendMethodToServiceName, open);
    }
    
    /**
     * @param baseDir 生成文件的主目录
     * @param overrideIfExist 如果已存在，是否覆盖？true-覆盖
     * @param open 是否打开文件夹
     */
    public void build(String baseDir, boolean appendMethodToServiceName, boolean open) {
        boolean overrideIfExist;
        if (appendMethodToServiceName) {
            overrideIfExist = true;
        } else {
            overrideIfExist = false;
        }
        if(UT.Str.isBlank(author)) {
            throw new IllegalArgumentException("author can not be null");
        }
        
        if(UT.Str.isBlank(baseDir)) {
            throw new IllegalArgumentException("baseDir can not be null");
        }
        
        String useServiceName = serviceName;
        if(!useServiceName.endsWith("Service")) {
            useServiceName = useServiceName  + "Service";
        }
        if (appendMethodToServiceName) {
            useServiceName = useServiceName + "_" + methodName;
        }
        
        ConfigBuilder config = new ConfigBuilder();
        config.setFileOverride(overrideIfExist);
        config.setBaseDir(baseDir);
        config.setEntityTmplPath(TEMPLATE_ENTITY);
        config.setServiceTmplPath(TEMPLATE_SERVICE);
        
        ServiceInfo serviceInfo = new ServiceInfo();
        serviceInfo.setPackageName(packageName);
        serviceInfo.setClassName(useServiceName);
        serviceInfo.setAuthor(author);
        serviceInfo.setMethodName(methodName);
        serviceInfo.setComment(comment);
        serviceInfo.setDtoPath(getModule().toLowerCase());
        
        config.setServiceInfo(serviceInfo);
        
        EntityInfo paramsInfo = serviceInfo.clone();
        paramsInfo.setClassName(serviceInfo.getReqObjectName());
        paramsInfo.setFields(params);
        paramsInfo.setComment(comment + " 请求类");
        Set<String> list = parseFieldMeta(params);
        list.add(ReqDto.class.getName());
        paramsInfo.setImportPackages(list);
        
        config.setParamsInfo(paramsInfo);
        
        EntityInfo resultsInfo = serviceInfo.clone();
        resultsInfo.setClassName(serviceInfo.getResObjectName());
        resultsInfo.setFields(results);
        resultsInfo.setComment(comment + " 返回类");
//        importPackages = new HashSet<>();
//        importPackages.add(Serializable.class.getName());
//        for(FieldMeta fd: results) {
//            tmp = fd.getType().getName();
//            if (!tmp.startsWith("java.lang") 
//                    && !tmp.startsWith("[Ljava.lang") 
//                    && tmp.indexOf(".") > 0) {
//                tmp = tmp.startsWith("[L") ? tmp.substring(2) : tmp;
//                importPackages.add(tmp);
//            }
//        }
//        resultsInfo.setImportPackages(importPackages);
        list = parseFieldMeta(results);
        list.add(ResDto.class.getName());
        resultsInfo.setImportPackages(list);
        
        config.setResultsInfo(resultsInfo);
        
        System.out.println("==========================准备生成文件...==========================");
        AbstractTemplateEngine templateEngine = new FreemarkerTemplateEngine();
        // 模板引擎初始化执行文件输出
        templateEngine.init(config).batchOutput();
        if (open) {
            templateEngine.open();
        }
        System.out.println("==========================文件生成完成！！！==========================");
        outputDir = null;
    }
    
        protected Set<String> parseFieldMeta(List<FieldMeta> list) {
            TreeSet<String> importPackages = new TreeSet<>();
//            importPackages.add(Serializable.class.getName());
            String tmp;
            boolean hasValidation = false;
            for (FieldMeta fd : list) {
                if (fd.getType() != null) {
                    tmp = fd.getType().getName();
                    if (!tmp.startsWith("java.lang") && !tmp.startsWith("[Ljava.lang") && tmp.indexOf(".") > 0) {
                        tmp = tmp.startsWith("[L") ? tmp.substring(2) : tmp;
                        importPackages.add(tmp);
                    }
                } else {

                    String propertyType = fd.getPropertyType();
                    propertyType = UT.Str.replace(propertyType, "java.lang.", "");
                    if(propertyType.indexOf(".") > 0) {
                        // 测试用例如下：
                        // java.util.List<java.lang.Long>[]
                        // java.util.List<java.util.Map<java.lang.String, java.lang.Long>>
                        // java.util.Map<java.lang.String, java.util.List<java.lang.String>>
                        if (propertyType.endsWith("[]")) {
                            propertyType = propertyType.substring(0, propertyType.length() - 3);
                            System.out.println(propertyType);
                        }
                        String arr[] = UT.Split.splitIgnoreEmpty(propertyType, "<>,");
                        String newTypeName = propertyType;
                        for (String name : arr) {
                            int i = name.lastIndexOf(".");
                            if (i > 0) {
                                String shortName = name.substring(i + 1, name.length());
                                System.out.println(shortName);
                                newTypeName = UT.Str.replace(newTypeName, name, shortName);
                                importPackages.add(name);
                            }
                        }
                        System.out.println(newTypeName);
                        fd.setPropertyType(newTypeName);
                    } else {
                        fd.setPropertyType(propertyType);
                    }

                    if (UT.Coll.isNotEmpty(fd.getAnnotations())) {
                        hasValidation = true;
                    }
                }
            }
            if(hasValidation) {
                importPackages.add("javax.validation.constraints.*");
            }
            
            return importPackages.descendingSet();
        }
    
    }

    public String getModule() {
        return module;
    }

    public void setModule(String module) {
        this.module = module;
    }
}
